/************************************************************************
*
* Title:
*
*   Main.C - IME Full-aware Simple Text Editor (DBCS version)
*
* Purpose:
*
*   Sample program for DBCS programming and IME full-aware aappliction.
*
* Synopsis:
*
*   This program is designed as a bare-bone example to demonstrate the
*   basic elements of DBCS-enabling, and how to design an IME full-aware
*   application.
*
*   The data structure is a fixed-size 2-dimensional byte array.  The
*   font is the fixed-pitch system font.  Rudimentary text editing
*   functions, such as the basic cursor movements, insertion, deletion,
*   have been implemented.
*
*   When you run this program in a Far East Windows environment, it
*   should be apparent that it doesn't handle DBCS character very well.
*   It is your job to locate and modify the pieces inside this program
*   that need to be DBCS-enabled.
*
* DBCS-enabling notes:
*
*   This version of STE is DBCS-enabled with respect to character input,
*   caret shape and movement, and mouse clicking.  It should work on
*   any version of Far East Windows since 3.0.
*
*   As far as source code maintenance goes, there are generally two
*   approaches.
*
*   The first is to add DBCS enabling code under '#ifdef DBCS'.  The
*   advantage of this approach is that it keeps the DBCS enabling code
*   distinct from the SBCS, so it's easier to add them in, and also to
*   remove them later.  (For example, when you want to replace DBCS
*   enabling with Unicode enabling.)  The drawback is that because the
*   DBCS and the SBCS logic are not integrated, they can easily get out
*   of sync (as the SBCS code evolves.)
*
*   The second approach, which is adopted by this sample app, is to
*   integrate DBCS enabling with SBCS.  It takes longer to do, but
*   the resulting source is easier to maintain.  Since IsDBCSLeadByte
*   is at the heart of any DBCS-enabling logic, an additional speed up
*   for generating an SBCS-only version is to define that function as
*   FALSE, and let the compiler optimize the DBCS logic away.
*
* IME Full-Aware notes:
*
*   This version of STE is an IME full-aware application with most of
*   IME UI capiabilities to display IME UI by itself. It should work
*   on any version of Far Windows since 4.0.
*
*   This kind of application typically wants to be fully responsible to
*   display any information given by IME. Ii will handle the input context
*   by itself and duisplay any necessary information given by the
*   input context not using IME UI.
*
* History:
*
*   17-Aug-1992  created
*   28-Sep-1992  added DBCS-enabling
*   30-Sep-1992  bug fixes
*   25-Mar-1994  added IME full-aware logics
*
************************************************************************/

#include <assert.h>
#include <windows.h>
#include <imm.h>
#include "FullIME.h"


/************************************************************************
*
*   SteRegister - standard class registration routine
*
************************************************************************/
int SteRegister( 
    HANDLE hInstance )
{
    BOOL bSuccess = TRUE;
    int WINAPI SteWndProc( HWND, UINT, UINT, LONG );

    PWNDCLASS pWndClass;

    pWndClass=(PWNDCLASS)LocalAlloc(LPTR, sizeof(WNDCLASS));
    if (NULL == pWndClass)
    {
        bSuccess = FALSE;
        goto exit_func;
    }

    pWndClass->hCursor       = LoadCursor( NULL, IDC_IBEAM );
    pWndClass->hIcon         = LoadIcon( hInstance, MAKEINTRESOURCE(ID_ICON));
    pWndClass->lpszMenuName  = MAKEINTRESOURCE(ID_MENU);
    pWndClass->hInstance     = hInstance;
    pWndClass->lpszClassName = szSteClass;
    pWndClass->lpfnWndProc   = (WNDPROC)SteWndProc;
    pWndClass->hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    pWndClass->style         = CS_BYTEALIGNCLIENT | CS_CLASSDC;

    if ( 0 == RegisterClass( (LPWNDCLASS)pWndClass ) )
    {
        bSuccess = FALSE;
        goto exit_func;
    }

    pWndClass->hCursor       = LoadCursor( NULL,IDC_ARROW );
    pWndClass->hIcon         = NULL;
    pWndClass->lpszMenuName  = NULL;
    pWndClass->hInstance     = hInstance;
    pWndClass->lpszClassName = szCandClass;
    pWndClass->lpfnWndProc   = (WNDPROC)CandWndProc;
    pWndClass->hbrBackground = GetStockObject( LTGRAY_BRUSH );
    pWndClass->style         = CS_HREDRAW | CS_VREDRAW;

    if ( 0 == RegisterClass( (LPWNDCLASS)pWndClass ) )
    {
        bSuccess = FALSE;
        goto exit_func;
    }

    if ( NULL != LocalFree( (HANDLE)pWndClass ) )
    {
        bSuccess = FALSE;
    }

exit_func:
    return bSuccess;
}


/************************************************************************
*
*   WinMain
*
************************************************************************/
int PASCAL WinMain(
    HINSTANCE hInstance,
    HINSTANCE hPrevInstance,
    LPSTR lpszCmdLine,
    int nCmdShow )
{
    MSG msg = {0};
    HWND hWnd = NULL;
    BOOL bRet;
    BOOL bSuccess = TRUE;


    LoadString( hInstance, IDS_CLASS, szSteClass, 14 );
    LoadString( hInstance, IDS_TITLE, szSteTitle, 55 );
    LoadString( hInstance, IDS_CANDUI, szSteCandUIClass, 12 );
    LoadString( hInstance, IDS_COMPTITLE, szSteCompTitle, 55 );
    LoadString( hInstance, IDS_CANDTITLE, szSteCandTitle, 55 );
    LoadString( hInstance, IDS_CANDCLASS, szCandClass, 20 );

    if ( !SteRegister( hInstance ) )
    {
        bSuccess = FALSE;
        goto exit_func;
    }

    if ( !(hWnd = CreateWindow( szSteClass, szSteTitle,
                                WS_OVERLAPPED | WS_CAPTION | WS_SYSMENU | WS_MINIMIZEBOX,
                                CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 
                                NULL, NULL, hInstance, NULL)) )
    {
        bSuccess = FALSE;
        goto exit_func;
    }

    //
    // Create window with just enough client space for the text buffer
    //
    SetWindowPos( hWnd, 0, 0, 0,
                  MAXCOL*cxMetrics + GetSystemMetrics(SM_CXBORDER)*2,
                  MAXROW*cyMetrics + GetSystemMetrics(SM_CYBORDER)*2
                    + GetSystemMetrics(SM_CYCAPTION)
                    + GetSystemMetrics(SM_CYMENU),
                  SWP_NOZORDER );
    ShowWindow( hWnd, nCmdShow );

    //
    // IME initial state.
    //
    gImeUIData.ImeState = 0;

    while( (bRet = GetMessage( &msg, NULL, 0, 0 )) != 0)
    { 
        if (bRet == -1)
        {
            // handle the error and possibly exit
            bSuccess = FALSE;
            goto exit_func;
        }
        else
        {
            TranslateMessage(&msg); 
            DispatchMessage(&msg); 
        }
    }

exit_func:
    if (bSuccess)
    {
        return (int)msg.wParam;
    }
    else
    {
        return 0;
    }
}


/************************************************************************
*
*   SteIMEOpenClose - This routines calls IMM API to open or close IME.
*
************************************************************************/
void SteImeOpenClose( 
    HWND hWnd, 
    BOOL fFlag )
{
	HIMC hIMC;

	//
	// If fFlag is true then open IME; otherwise close it.
	//

    if ( !( hIMC = ImmGetContext( hWnd ) ) )
        return;

    ImmSetOpenStatus( hIMC, fFlag );
    ImmReleaseContext( hWnd, hIMC );
}


/************************************************************************
*
*   SteCreate - WM_CREATE message handler
*
************************************************************************/
void SteCreate( 
    HWND hWnd )
{
    HDC        hdc;
    TEXTMETRIC tm = {0};
    int        i;
    WORD       patern = 0xA4A4;
    SIZE       size;
    HFONT      hFont;
    LOGFONT    lFont = {0};

    //
    // Note that this window has a class DC
    //

    hdc = GetDC( hWnd );

    //
    // Select fixed pitch system font and get its text metrics
    //

    hfntFixed = GetStockObject( SYSTEM_FIXED_FONT );
    hfntOld = SelectObject( hdc, hfntFixed );
    GetTextMetrics( hdc, &tm );
    ReleaseDC( hWnd, hdc );

    GetTextExtentPoint( hdc, (LPSTR)&patern, sizeof( WORD), (LPSIZE) &size );

//    cxMetrics = tm.tmAveCharWidth;
//    cyMetrics = tm.tmHeight;

    cxMetrics = (UINT) size.cx / 2;
    cyMetrics = (UINT) size.cy;

    //
    // Determine the version of DBCS Windows from system font charset ID.
    // Then hardcode a DBCS character value for the 'Pattern/DBCS' command.
    // The value is the Han character for 'door' or 'gate', which is
    // left-right symmetrical.
    //

    switch( tm.tmCharSet )
    {
    case SHIFTJIS_CHARSET:
        DBCSFillChar = 0x96e5;
        break;

    case HANGEUL_CHARSET:
        DBCSFillChar = 0xdaa6;
        break;

    case CHINESEBIG5_CHARSET:
        DBCSFillChar = 0xaaf9;
        break;

    default:
        DBCSFillChar = 0x7071;  // 'pq'
        break;
    }

    //                   
    // Initialize caret width.  Fat in INSERT mode, skinny in OVERTYPE mode.
    //

    fInsertMode = FALSE;
    CaretWidth = cxOverTypeCaret = GetSystemMetrics( SM_CXBORDER );

    //
    // Sets the logical font to be used to display characters in the 
    // composition window. Especially for at caret or near caret operation, 
    // application should set composition font.
    //
    // If Application provides user to dynamicly change font, each time after
    // user change font, application should set composition font again.
    //

    if ( ( hFont = (HFONT)SendMessage( hWnd, WM_GETFONT, 0, 0L ) ) != NULL )
    {
        if ( GetObject( hFont, sizeof(LOGFONT), (LPVOID)&lFont ) )
        {
            HIMC	hImc;

            if ( (  hImc = ImmGetContext( hWnd ) ) )
            {
                ImmSetCompositionFont( hImc, &lFont );
                ImmReleaseContext( hWnd, hImc );
            }
        }
    }

    //
    // Get the property and apiabilities of current keyboard layout(IME).
    // If the keyboard layout is US, the return value will be zero.
    //

    gImeUIData.fdwProperty = ImmGetProperty( GetKeyboardLayout(0),
                                             IGP_PROPERTY );

    //
    // Initialize candidate list window array.
    //

    for( i = 0; i < MAX_LISTCAND; i++ )
    {
         gImeUIData.hListCand[ i ] = NULL;
         gImeUIData.hListCandMem[ i ] = NULL;
    }


    PostMessage( hWnd, WM_COMMAND, IDC_CLEAR, 0L );


    //
    // Initialise the current keyboard layout.
    //
    
    hCurKL = GetKeyboardLayout(0L);
}


/************************************************************************
*
*   ResetCaret - Reset caret shape to match input mode (overtype/insert)
*
************************************************************************/
void ResetCaret( 
    HWND hWnd )
{
    HideCaret( hWnd );
    DestroyCaret();
    CreateCaret( hWnd,
                 NULL,
                 (fInsertMode && IsDBCSLeadByte( textbuf[yPos][xPos] )) ?
                 CaretWidth*2 : CaretWidth,
                 cyMetrics );
    SetCaretPos( xPos * cxMetrics, yPos * cyMetrics );

    if ( !( gImeUIData.fdwProperty & IME_PROP_AT_CARET ) &&
         !( gImeUIData.fdwProperty & IME_PROP_SPECIAL_UI ) )
    {
        // near caret.
        SetIMECompFormPos( hWnd );
    }
 
    ShowCaret( hWnd );
}

/************************************************************************
*
*   SetIMECompFormPos 
*
************************************************************************/
void SetIMECompFormPos( 
    HWND hWnd )
{
    HIMC hIMC = ImmGetContext(hWnd);
    POINT point = {0};
    COMPOSITIONFORM CompForm;

    GetCaretPos( &point );

    CompForm.dwStyle = CFS_POINT;

    CompForm.ptCurrentPos.x = (long) point.x;
    CompForm.ptCurrentPos.y = (long) point.y;

    if ( hIMC )
        ImmSetCompositionWindow(hIMC,&CompForm);
    ImmReleaseContext( hWnd , hIMC);
}


/************************************************************************
*
*   SteCommand - WM_COMMAND handler
*
************************************************************************/
void SteCommand(
    HWND hWnd, 
    UINT cmd, 
    LPARAM lParam )
{
    switch( cmd )
    {
    case IDC_CLEAR:
        //
        // Blank out text buffer.  Return caret to home position
        //
        for ( yPos = FIRSTROW; yPos <= LASTROW; yPos++ )
            for ( xPos = FIRSTCOL; xPos <= LASTCOL; xPos++ )
        	textbuf[yPos][xPos] = ' ';
        break;

    case IDC_ANSIFILL: /* fall through */
    case IDC_DBCSFILL:
        //
        // Fill text buffer with ANSI or DBCS pattern
        //
        for ( yPos = FIRSTROW; yPos <= LASTROW; yPos++ )
            for ( xPos = FIRSTCOL; xPos <= LASTCOL; xPos++ )
        	if ( cmd == IDC_ANSIFILL )
        	    textbuf[yPos][xPos] = 'a';
        	else {
        	    textbuf[yPos][xPos]   = HIBYTE(DBCSFillChar);
        	    textbuf[yPos][++xPos] = LOBYTE(DBCSFillChar);
        	}
        break;
    
    //
    // The following messages are to control IME.
    //
    case IDC_OPENIME:
        SteImeOpenClose( hWnd, TRUE );
        goto exit_func;

    case IDC_CLOSEIME:
        SteImeOpenClose( hWnd, FALSE );
        goto exit_func;

    }

    yPos = FIRSTROW;
    xPos = FIRSTCOL;

    InvalidateRect( hWnd, (LPRECT)NULL, TRUE );
    ResetCaret(hWnd);

exit_func:
    return;
}


/************************************************************************
*
*   IsDBCSTrailByte - returns TRUE if the given byte is a DBCS trail byte
*
*                     The algorithm searchs backward in the string, to some
*                     known character boundary, counting consecutive bytes
*                     in the lead byte range. An odd number indicates the
*                     current byte is part of a two byte character code.
*
*   INPUT: PCHAR  - pointer to a preceding known character boundary.
*          PCHAR  - pointer to the character to test.
*
*   OUTPUT:BOOL   - indicating truth of p==trailbyte.
*
************************************************************************/

BOOL IsDBCSTrailByte( 
    char *base, 
    char *p )
{
    int lbc = 0;    // lead byte count

    assert(base <= p);

    while ( p > base ) 
    {
        if ( !IsDBCSLeadByte(*(--p)) )
            break;
        lbc++;
    }

    return (lbc & 1);
}


/************************************************************************
*
*   MoveCaret
*
************************************************************************/
BOOL MoveCaret( 
    HWND hwnd )
{
    HIMC hIMC;
    BOOL retVal = TRUE;

    if ( !( hIMC = ImmGetContext( hwnd ) ) )
        goto exit_func;
	
    if ( ImmGetCompositionString( hIMC, GCS_CURSORPOS, (void *)NULL, 0 ) )
        retVal = FALSE;                                    
	
    ImmReleaseContext( hwnd, hIMC );

exit_func:
    return retVal;
}


/************************************************************************
*
*   VirtualKeyHandler - WM_KEYDOWN handler
*
*
*   INPUT:  HWND - handle to the window for repainting output.
*           UINT - virtual key code.
*
************************************************************************/

void VirtualKeyHandler( 
    HWND hWnd, 
    UINT wParam )
{
    int i;
    HDC hdc;
    static int delta = 1;

    if ( ( gImeUIData.ImeState & IME_IN_CHOSECAND ) ||
         ( gImeUIData.ImeState & IME_IN_COMPOSITION && !MoveCaret( hWnd ) ) )
        return;

    switch( wParam )
    {
    case VK_HOME:   // beginning of line
        xPos = FIRSTCOL;
        break;

    case VK_END:    // end of line
        xPos = LASTCOL;
        goto check_for_trailbyte;

    case VK_RIGHT:
        if ( IsDBCSLeadByte( textbuf[yPos][xPos] ) )
        {
            if (xPos==LASTCOL - 1) break;  //last character don't move
            xPos += 2;                     //skip 2 for DB Character
        }
        else
        {
            xPos = min( xPos + 1, LASTCOL );
        }
        break;

    case VK_LEFT:
        xPos = max( xPos - 1, FIRSTCOL );
check_for_trailbyte:
    	if ( IsDBCSTrailByte( (LPSTR)textbuf[yPos], (LPSTR)&(textbuf[yPos][xPos]) ) )
    	    xPos--;
    	break;

    case VK_UP:
        yPos = max( yPos - 1, FIRSTROW );
        goto Virtical_Check_Trail;

    case VK_DOWN:
        yPos = min( yPos + 1, LASTROW );
Virtical_Check_Trail:
        if ( IsDBCSTrailByte( (LPSTR)textbuf[yPos], (LPSTR)&(textbuf[yPos][xPos]) ) )
        {
            if (xPos<LASTCOL)
            {
                xPos+=delta;
                delta *= -1;
            }
            else
            {
                xPos--;
            }
        }
        break;


    case VK_INSERT:
        //
        // Change caret shape to indicate insert/overtype mode
        //
        fInsertMode = !fInsertMode;
        CaretWidth = fInsertMode ? cxMetrics : cxOverTypeCaret;
        break;


    case VK_BACK:   // backspace

        if ( xPos > FIRSTCOL ) 
        {
            xPos--;

            //
            // DB Character so backup one more to allign on boundary
            //
            if ( IsDBCSTrailByte( (LPSTR)textbuf[yPos], (LPSTR)&(textbuf[yPos][xPos]) ) )
        	xPos--;
            //
            // Fall Through to VK_DELETE to adjust row
            //
        }
        else     //FIRST COLUMN  don't backup -- this would change for wrapping
        {
           break;
        }

    case VK_DELETE:

        if ( !IsDBCSLeadByte( textbuf[yPos][xPos] ) ) 
        {
            //
            // Move rest of line left by one, then blank out last character
            //
            for ( i = xPos; i < LASTCOL; i++ )
            {
                textbuf[yPos][i] = textbuf[yPos][i+1];
            }
            textbuf[yPos][LASTCOL] = ' ';

        } 
        else 
        {
            //
            // Move line left by two bytes, blank out last two bytes
            //
            for ( i = xPos; i < LASTCOL-1; i++ )
            {
        	    textbuf[yPos][i] = textbuf[yPos][i+2];
            }
            textbuf[yPos][LASTCOL-1] = ' ';
            textbuf[yPos][LASTCOL]   = ' ';
        }

        //
        // Repaint the entire line
        //
        hdc = GetDC( hWnd );
        HideCaret( hWnd );
        TextOut( hdc, 0, yPos*cyMetrics, (LPSTR)textbuf[yPos], MAXCOL );
        ReleaseDC( hWnd, hdc );
        break;

    case VK_TAB:    // tab  -- tabs are column allignment not character
        {
            int xTabMax = xPos + TABSTOP;
            int xPosPrev;

            do 
            {
                xPosPrev = xPos;
                SendMessage( hWnd, WM_KEYDOWN, VK_RIGHT, 1L );
            } while ( (xPos % TABSTOP) &&
                      (xPos < xTabMax) &&
                      (xPos != xPosPrev));

        }
        break;

    case VK_RETURN: // linefeed
        yPos = min( yPos+1, LASTROW );
        xPos = FIRSTCOL;
        break;
    }

    ResetCaret( hWnd );
}


/************************************************************************
*
*   StoreChar - Stores one SBCS character into text buffer and advances
*               cursor
*
************************************************************************/
void StoreChar( 
    HWND hWnd, 
    UCHAR ch )
{
    int i;
    HDC hdc;

    //
    // If insert mode, move rest of line to the right by one
    //

    if ( fInsertMode ) 
    {
	    for ( i = LASTCOL; i > xPos; i-- )
	        textbuf[yPos][i] = textbuf[yPos][i-1];

    	//
    	// If the row ends on a lead byte, blank it out
    	// To do this we must first traverse the string
    	// starting from a known character boundry until
    	// we reach the last column. If the last column
    	// is a character boundry then the last character
    	// is either a single byte or a lead byte
    	//
        for ( i = xPos+1; i < LASTCOL; ) 
        {
            if ( IsDBCSLeadByte( textbuf[yPos][i] ) )
            {
                i++;
            }
            i++;
	    }
        
        if (i==LASTCOL)
            if ( IsDBCSLeadByte( textbuf[yPos][LASTCOL] ) )
                textbuf[yPos][LASTCOL] = ' ';
    } 
    else 
    {  // overtype mode
    	if ( IsDBCSLeadByte( textbuf[yPos][xPos] ) )

    	    //
    	    // Blank out trail byte
    	    //
    	    textbuf[yPos][xPos+1] = ' ';

    	    //
    	    // or shift line left on character and blank last column
    	    //
    	    // for ( i = xPos+1; i < LASTCOL; i++ )
    	    //     textbuf[yPos][i] = textbuf[yPos][i+1];
    	    // textbuf[yPos][LASTCOL] = ' ';
    }

    //
    // Store input character at current caret position
    //
    textbuf[yPos][xPos] = ch;


    //
    // Display input character.
    //
    hdc = GetDC( hWnd );
    HideCaret( hWnd );
    TextOut( hdc, xPos*cxMetrics, yPos*cyMetrics,
             (LPSTR)&(textbuf[yPos][xPos]), MAXCOL-xPos );
    ShowCaret( hWnd );
    ReleaseDC( hWnd, hdc );

    SendMessage( hWnd, WM_KEYDOWN, VK_RIGHT, 1L );
}


/************************************************************************
*
*   StoreDBCSChar - Stores one DBCS character into text buffer and
*                   advances cursor
*
************************************************************************/
void StoreDBCSChar( 
    HWND hWnd, 
    WORD ch )
{
    int i;
    HDC hdc;

    //
    // If there is no room for a DBCS character, discard it
    //
    if ( xPos == LASTCOL )
        return;

    //
    // If insert mode, move rest of line to the right by two
    //

    if ( fInsertMode ) 
    {
        for ( i = LASTCOL; i > xPos+1; i-- )
	        textbuf[yPos][i] = textbuf[yPos][i-2];

    	//
    	// If the row ends on a lead byte, blank it out
    	// To do this we must first traverse the string
    	// starting from a known charcter boundry until
    	// we reach the last column. If the last column
    	// is not a trail byte then it is a single byte
    	// or a lead byte
    	//

        for ( i = xPos+2; i < LASTCOL; ) 
        {
            if ( IsDBCSLeadByte( textbuf[yPos][i] ) )
                i++;
            i++;
	    }
        
        if (i==LASTCOL)
            if (IsDBCSLeadByte( textbuf[yPos][LASTCOL] ) )
                textbuf[yPos][LASTCOL] = ' ';
    }
    else 
    {  // overtype mode
    	if ( !IsDBCSLeadByte( textbuf[yPos][xPos] ) )

        //
        // Overtyping the current byte, plus the following byte,
        // which could be a lead byte.
        //

        if ( IsDBCSLeadByte( textbuf[yPos][xPos+1] ) )
            textbuf[yPos][xPos+2] = ' ';
    }

    //
    // Store input character at current caret position
    //
    textbuf[yPos][xPos]   = LOBYTE(ch);     // lead byte
    textbuf[yPos][xPos+1] = HIBYTE(ch);     // trail byte

    //
    // Display input character.
    //

    hdc = GetDC( hWnd );
    HideCaret( hWnd );
    TextOut( hdc, xPos*cxMetrics, yPos*cyMetrics,
             (LPSTR)&(textbuf[yPos][xPos]), MAXCOL-xPos );
    ShowCaret( hWnd );
    ReleaseDC( hWnd, hdc );

    SendMessage( hWnd, WM_KEYDOWN, VK_RIGHT, 1L );
}


/************************************************************************
*
*   CharHandler - WM_CHAR handler
*
************************************************************************/
void CharHandler( 
    HWND hWnd, 
    WORD wParam )
{
    unsigned char ch = (unsigned char)wParam;

    //
    // Because DBCS characters are usually generated by IMEs (as two
    // PostMessages), if a lead byte comes in, the trail byte should
    // arrive very soon after.  We wait here for the trail byte and
    // store them into the text buffer together.

    if ( IsDBCSLeadByte( ch ) ) 
    {
    	//
    	// Wait an arbitrary amount of time for the trail byte to
    	// arrive.  If it doesn't, then discard the lead byte.
    	//
    	// This could happen if the IME screwed up.  Or, more likely,
    	// the user generated the lead byte through ALT-numpad.
    	//

        MSG msg = {0};
        int i = 10;

        while (!PeekMessage((LPMSG)&msg, hWnd, WM_CHAR, WM_CHAR, PM_REMOVE)) 
        {
            if ( --i == 0 )
                return;
            Yield();
        }

        StoreDBCSChar( hWnd,  (WORD)(((unsigned)(msg.wParam)<<8) | (unsigned)ch ));
    } 
    else 
    {
        switch( ch )
        {
            case '\r': 
            case '\t':
            case '\b':
                //
                // Throw away.  Already handled at WM_KEYDOWN time.
                //
                break;

            default:
                StoreChar( hWnd, ch );
                break;
        }
    }
}


/************************************************************************
*
*   MouseHandler - WM_BUTTONDOWN handler
*
************************************************************************/
void MouseHandler( 
    HWND hWnd, 
    LONG lParam )
{

    if ( ( gImeUIData.ImeState & IME_IN_CHOSECAND ) ||
         ( gImeUIData.ImeState & IME_IN_COMPOSITION && !MoveCaret( hWnd ) ) )
        return;

    HideCaret( hWnd );

    //
    // Calculate caret position based on fixed pitched font
    //
    yPos = MAKEPOINTS(lParam).y / cyMetrics;
    xPos = MAKEPOINTS(lParam).x / cxMetrics;

    //
    // Adjust caret position if click landed on a trail byte
    //
    if ( IsDBCSTrailByte( (LPSTR)textbuf[yPos], (LPSTR)&(textbuf[yPos][xPos]) ) )
    {
        //
        // If click landed on the last quarter of the DBCS character,
        // assume the user was aiming at the next character.
        //
        if ( (MAKEPOINTS(lParam).x - xPos * cxMetrics) > (cxMetrics / 2) )
            xPos++;
        else
            xPos--;
    }
    
    DestroyCaret();
    CreateCaret(hWnd, NULL,
                (fInsertMode && IsDBCSLeadByte( textbuf[yPos][xPos] )) ? CaretWidth*2 : CaretWidth,
                cyMetrics );
    SetCaretPos( xPos * cxMetrics, yPos * cyMetrics );
    ShowCaret( hWnd );
}


/************************************************************************
*
*   InputChangeHandler - WM_INPUTLANGCHANGE handler
*
************************************************************************/
void InputChangeHandler( 
    HWND hWnd )
{
    HIMC hIMC;

    // 
    // If the old keyboard layout is IME, the ime ui data have to be free.
    // 
       
    if (ImmIsIME(hCurKL))
    {
        //
        // If application prefers to use near caret provded by IME, or
        // IME provides special UI, then no need to clean UD data.
        // 
        if ( gImeUIData.fdwProperty & IME_PROP_SPECIAL_UI )
            ;
        else if ( gImeUIData.fdwProperty & IME_PROP_AT_CARET )
            ImeUIClearData(hWnd);
        else
            ;
    }

    //
    // Set new keyboard layout.
    //
    hCurKL = GetKeyboardLayout(0L);

    //
    // Get new property.
    //
    gImeUIData.fdwProperty = ImmGetProperty( hCurKL, IGP_PROPERTY );

    // if this application set the candidate position, it need to set
    // it to default for the near caret IME

    if ( hIMC = ImmGetContext( hWnd ) ) 
    {
        UINT i;

        for (i = 0; i < 4; i++) 
        {
            CANDIDATEFORM CandForm;

            if ( gImeUIData.fdwProperty & IME_PROP_AT_CARET ) 
            {
                CandForm.dwIndex = i;
                CandForm.dwStyle = CFS_CANDIDATEPOS;

#if 0
                // This application dooes not want to set candidate window 
                // to any position. If an application need to set the
                // candidate position, it should remove the if 0 code

                // the position you want to set
                CandForm.ptCurrentPos.x = ptAppWantPosition[i].x;
                CandForm.ptCurrentPos.y = ptAppWantPosition[i].y;

                ImmSetCandidateWindow( hIMC, &CandForm );
#endif
            } 
            else 
            {
                if ( !ImmGetCandidateWindow( hIMC, i, &CandForm ) ) 
                {
                    continue;
                }

                if ( CandForm.dwStyle == CFS_DEFAULT ) 
                {
                    continue;
                }

                CandForm.dwStyle = CFS_DEFAULT;
                ImmSetCandidateWindow( hIMC, &CandForm );
            }
        }

        ImmReleaseContext( hWnd, hIMC );
    }

    return;
}


/************************************************************************
*
*   SteWndProc - STE class window procedure
*
************************************************************************/
int WINAPI SteWndProc( HWND hWnd, UINT msg, UINT wParam, LONG lParam )
{
    int i;
    HDC hdc;
    PAINTSTRUCT ps = {0};

    switch( msg ) 
    {
    case WM_CREATE:
        SteCreate( hWnd );
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_CLOSE:
        DestroyWindow( hWnd );
        break;

    case WM_SETFOCUS:
        CreateCaret( hWnd, NULL,
                     (fInsertMode && IsDBCSLeadByte( textbuf[yPos][xPos] )) ?
                         CaretWidth*2 : CaretWidth,
                     cyMetrics );
        SetCaretPos( xPos * cxMetrics, yPos * cyMetrics );
        ShowCaret( hWnd );
        break;

    case WM_KILLFOCUS:
        HideCaret( hWnd );
        DestroyCaret();
        break;

    case WM_IME_KEYDOWN: /* fall-through */
    case WM_KEYDOWN:
        VirtualKeyHandler( hWnd, wParam );
        break;

    case WM_KEYUP:
        break;

    case WM_CHAR:
        CharHandler( hWnd, (WORD)wParam );
        break;

    case WM_LBUTTONDOWN:
        MouseHandler( hWnd, lParam );
        break;

    case WM_MOVE:
        ImeUIMoveCandWin( hWnd );
        break;

    case WM_COMMAND:
        SteCommand( hWnd, wParam, lParam );
        break;

    case WM_PAINT:
        InvalidateRect(hWnd,NULL,FALSE);  //for repaint allignment problem??
    				  // WinChi3.0
        hdc = BeginPaint( hWnd, &ps );

        //
        // Refresh display from text buffer
        //
        for ( i = FIRSTROW; i <= LASTROW; i++ )
            TextOut( hdc, 0, i*cyMetrics, (LPSTR)textbuf[i], MAXCOL );

        EndPaint( hWnd, &ps );
        RestoreImeUI( hWnd );
        break;

    case WM_INPUTLANGCHANGE:
        InputChangeHandler( hWnd );
        goto call_defwinproc;
        break;

    case WM_IME_SETCONTEXT:
        //
        // The application have to pass WM_IME_SETCONTEXT to DefWindowProc.
        // When the application want to handle the IME at the timing of
        // focus changing, the application should use WM_SETFOCUS or
        // WM_KILLFOCUS.
        //
     
        if ( gImeUIData.fdwProperty & IME_PROP_SPECIAL_UI )
        {
            goto call_defwinproc;
        }
        else if ( gImeUIData.fdwProperty & IME_PROP_AT_CARET )
        {
            //
            // application wants to draw UI ny itself.
            //
            lParam &= ~(ISC_SHOWUICOMPOSITIONWINDOW | ISC_SHOWUIALLCANDIDATEWINDOW);
        }
        goto call_defwinproc;

    case WM_IME_STARTCOMPOSITION:
        //
        // CheckProperty is a macro, if IME already provides near caret or
        // special UI then let IME handle this message.
        //
        CheckProperty;
        ImeUIStartComposition( hWnd );
        break;

    case WM_IME_COMPOSITION:
        CheckProperty;
        ImeUIComposition( hWnd, wParam, lParam );
        break;

    case WM_IME_ENDCOMPOSITION:
        CheckProperty;
        ImeUIEndComposition( hWnd );
        break;

    case WM_IME_COMPOSITIONFULL:
        //
        // Make sure the size for drawing the composition string.
        // Application should draw the composition string correctly.
        // 
        break;

    case WM_IME_NOTIFY:
        CheckProperty;
        if ( !ImeUINotify( hWnd, wParam, lParam ) )
            // This application does not handle all notification message.
            // So we pass those notification messages which are not hanlded
            // by this application to the DefWindowProc.
            goto call_defwinproc;
        break;
        

    case WM_IME_CONTROL:
        //
        // This message is not received by the application window.
        // But don't pass it to DefWindowProc().
        //
        break;


    default:

call_defwinproc:
        return (int)DefWindowProc( hWnd, msg, wParam, lParam );
    }

    return 0;
}
